package com.ssy.lingxi.message.serviceimpl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ssy.lingxi.common.model.dto.UserLoginCacheDTO;
import com.ssy.lingxi.common.response.PageData;
import com.ssy.lingxi.common.response.ResponseCode;
import com.ssy.lingxi.common.response.Wrapper;
import com.ssy.lingxi.member.merchant.api.feign.inner.MemberInnerControllerFeign;
import com.ssy.lingxi.member.merchant.api.model.vo.inner.response.MemberFeignImUserVO;
import com.ssy.lingxi.member.merchant.api.model.vo.inner.response.MemberFeignUserVO;
import com.ssy.lingxi.message.api.constant.WsAction;
import com.ssy.lingxi.message.entity.ImChatRecordDO;
import com.ssy.lingxi.message.entity.ImSessionDO;
import com.ssy.lingxi.message.entity.ImVisitorDO;
import com.ssy.lingxi.message.im.TLSSigAPIv2;
import com.ssy.lingxi.message.model.dto.ImGetChatRecordDTO;
import com.ssy.lingxi.message.model.dto.ImNotifyDTO;
import com.ssy.lingxi.message.model.vo.HistorySessionVO;
import com.ssy.lingxi.message.model.vo.ImChatRecordVO;
import com.ssy.lingxi.message.model.vo.UserSigVO;
import com.ssy.lingxi.message.model.vo.UserVO;
import com.ssy.lingxi.message.repository.ImChatRecordRepository;
import com.ssy.lingxi.message.repository.ImSessionRepository;
import com.ssy.lingxi.message.repository.ImVisitorRepository;
import com.ssy.lingxi.message.service.ImService;
import com.ssy.lingxi.message.websocket.WsMessagePushService;
import com.ssy.lingxi.message.websocket.model.WsMessage;
import com.ssy.lingxi.message.websocket.session.WsSessionKey;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * im - 业务实现层
 * @author fhj
 * @date 2021/03/22
 * @version 2.0.0
 */
@Slf4j
@Service
public class ImServiceImpl implements ImService {

    @Value("${im.sdkAppId}")
    private Long sdkAppId;
    @Value("${im.key}")
    private String key;
    @Resource
    private ImSessionRepository imSessionRepository;
    @Resource
    private MemberInnerControllerFeign memberInnerControllerFeign;
    @Resource
    private ImChatRecordRepository imChatRecordRepository;
    @Resource
    private ImVisitorRepository imVisitorRepository;
    @Resource
    private WsMessagePushService wsMessagePushService;

    /**
     * @param source 来源：1.PC；2.APP；
     * */
    @Override
    public Wrapper<List<HistorySessionVO>> all(Integer source, UserLoginCacheDTO user) {
        //获取当前登录用户的IM会话
        List<ImSessionDO> imSessionDOList = imSessionRepository.findByMemberIdAndUserIdAndSource(
                user.getMemberId(),
                user.getUserId(),
                source,
                Sort.by("updateTime").descending()
        );
        if(CollectionUtil.isEmpty(imSessionDOList)) {
            log.info("当前用户没有与任何一个客服聊天过");
            return Wrapper.success(new ArrayList<>());
        }

        //调用会员服务 -> 用根据户ID批量查询用户信息
        Wrapper<List<MemberFeignUserVO>> wrapper = memberInnerControllerFeign.getUsers(
                imSessionDOList.stream().map(ImSessionDO::getSessionUserId).collect(Collectors.toList())
        );
        if(ResponseCode.SUCCESS.getCode() != wrapper.getCode()) {
            log.error("调用会员服务失败：{}", wrapper.getMessage());
            return Wrapper.fail(ResponseCode.SERVICE_MERCHANT_MEMBER_ERROR);
        }
        if(CollectionUtil.isEmpty(wrapper.getData())) {
            log.info("获取不到用户信息");
            return Wrapper.success(new ArrayList<>());
        }

        //key=会话会员ID, value为每个会话会员的用户
        Map<Long, List<MemberFeignUserVO>> memberFeignUserVOMap = wrapper.getData().stream().collect(Collectors.groupingBy(MemberFeignUserVO::getMemberId));

        List<HistorySessionVO> list = new ArrayList<>();
        memberFeignUserVOMap.keySet().forEach(a -> {
            List<MemberFeignUserVO> memberFeignUserVOList = memberFeignUserVOMap.get(a);

            HistorySessionVO historySessionVO = new HistorySessionVO();
            historySessionVO.setMemberId(String.valueOf(memberFeignUserVOList.get(0).getMemberId()));
            historySessionVO.setMemberName(memberFeignUserVOList.get(0).getMemberName());
            historySessionVO.setMemberLogo(memberFeignUserVOList.get(0).getMemberLogo());
            historySessionVO.setUserList(
                    memberFeignUserVOList.stream().map(u -> {
                        UserVO userVO = new UserVO();
                        userVO.setUserId(String.valueOf(u.getUserId()));
                        userVO.setUserName(u.getUserName());
                        userVO.setUserLogo(u.getUserLogo());
                        userVO.setParentMemberId(String.valueOf(u.getMemberId()));
                        userVO.setParentMemberName(u.getMemberName());
                        userVO.setStatus(true);
                        return userVO;
                    }).collect(Collectors.toList())
            );

            list.add(historySessionVO);
        });

        return Wrapper.success(list);
    }

    /**
     * @param memberId 会话会员ID
     * @param source 来源：1.PC；2.APP；
     * */
    @Override
    public Wrapper<Void> add(Long memberId, Integer source, UserLoginCacheDTO user) {
        //调用会员服务 -> 根据会员ID批量查询IM客服用户
        Wrapper<List<MemberFeignImUserVO>> wrapper = memberInnerControllerFeign.getImUsers(
                Stream.of(memberId).collect(Collectors.toList())
        );
        if(ResponseCode.SUCCESS.getCode() != wrapper.getCode()) {
            log.error("调用会员服务失败：{}", wrapper.getMessage());
            return Wrapper.fail(ResponseCode.SERVICE_MERCHANT_MEMBER_ERROR);
        }
        if(CollectionUtil.isEmpty(wrapper.getData())) {
            log.info("获取不到用户信息");
            return Wrapper.fail(ResponseCode.SERVICE_MERCHANT_MEMBER_ERROR);
        }

        //获取当前登录用户的IM会话，且根据会话会员ID来筛选
        List<ImSessionDO> imSessionDOList = imSessionRepository.findByMemberIdAndUserIdAndSourceAndSessionMemberId(
                user.getMemberId(),
                user.getUserId(),
                source,
                memberId,
                Sort.by("updateTime").descending()
        );

        //加入当前用户的IM会话
        List<MemberFeignImUserVO> memberFeignImUserVOList = wrapper.getData();
        if(CollectionUtil.isNotEmpty(imSessionDOList)) {
            List<Long> sessionUserIdList = imSessionDOList.stream().map(ImSessionDO::getSessionUserId).collect(Collectors.toList());
            memberFeignImUserVOList = wrapper.getData().stream().filter(a -> !sessionUserIdList.contains(a.getUserId())).collect(Collectors.toList());
        }
        if(CollectionUtil.isNotEmpty(memberFeignImUserVOList)) {
            imSessionRepository.saveAll(
                    memberFeignImUserVOList.stream().map(a -> {
                        ImSessionDO imSessionDO = new ImSessionDO();
                        imSessionDO.setMemberId(user.getMemberId());
                        imSessionDO.setUserId(user.getUserId());
                        imSessionDO.setSessionMemberId(a.getMemberId());
                        imSessionDO.setSessionUserId(a.getUserId());
                        imSessionDO.setSource(1);
                        imSessionDO.setUpdateTime(System.currentTimeMillis());
                        return imSessionDO;
                    }).collect(Collectors.toList())
            );
        }

        return Wrapper.success();
    }


    @Override
    @ApiOperation(value = "获取UserSig")
    public Wrapper<UserSigVO> getUserSig(String userId, UserLoginCacheDTO user) {
        UserSigVO vo = new UserSigVO();
        if(StringUtils.isBlank(userId)) {
            ImVisitorDO imVisitorDO = new ImVisitorDO();
            imVisitorRepository.saveAndFlush(imVisitorDO);
            vo.setUserId(String.valueOf(imVisitorDO.getId()));
            vo.setUserName("游客" + vo.getUserId());
        }else {
            vo.setUserId(userId);
            vo.setUserName(user != null ? user.getCompany() : null);
        }
        vo.setLogo(user != null ? user.getLogo() : "");
        vo.setUserSig(new TLSSigAPIv2(sdkAppId, key).genUserSig(vo.getUserId(), 180 * 86400));
        return Wrapper.success(vo);
    }

    @Override
    @Transactional
    @ApiOperation(value = "IM回调")
    public String notify(ImNotifyDTO dto, HttpServletRequest request) {
        //读取参数
        String sdkAppid = request.getParameter("SdkAppid");
        String callbackCommand = request.getParameter("CallbackCommand");

        //打印日志
        log.info("------------IM回调通知--------------");
        log.info("sdkAppid: " + sdkAppid);
        log.info("callbackCommand: " + callbackCommand);
        log.info("body: " + dto.toString());

        //初始化返回结果
        Map<String, Object> result = new HashMap<>();
        result.put("ActionStatus", "OK");
        result.put("ErrorInfo", "OK");
        result.put("ErrorCode", 0);

        //校验sdkAppid
        if(!sdkAppid.equals(String.valueOf(sdkAppId))) {
            result.put("ActionStatus", "FAIL");
            result.put("ErrorInfo", "sdkAppId不匹配");
            result.put("ErrorCode", 1);
            return JSONUtil.toJsonStr(result);
        }

        //校验匹配命令（目前只有发单聊消息之后回调）
        if(callbackCommand.equals("C2C.CallbackAfterSendMsg")) {
            //保存IM聊天记录
            ImChatRecordDO imChatRecordDO = new ImChatRecordDO();
            imChatRecordDO.setFromAccount(dto.getFrom_Account());
            imChatRecordDO.setToAccount(dto.getTo_Account());
            imChatRecordDO.setMsgTime(dto.getMsgTime());
            imChatRecordDO.setMsgKey(dto.getMsgKey());
            imChatRecordDO.setSendMsgResult(dto.getSendMsgResult());
            imChatRecordDO.setErrorInfo(dto.getErrorInfo());
            imChatRecordDO.setUnreadMsgNum(dto.getUnreadMsgNum());

//            [
//              {MsgType=TIMCustomElem, MsgContent={Desc={"fromMemberId":"150", "toMemberId":"254"}, Data={"text":"你好"}, Ext=, Sound=}}
//            ]
            //回调接收的参数MsgBody是数组类型, 不过每次都只会有一个, 获取到msgBody
            JSONObject msgBody = dto.getMsgBody().get(0);

//            MsgContent={Desc={"fromMemberId":"150", "toMemberId":"254"}, Data={"text":"你好"}, Ext=, Sound=}
            //MsgType约定好都以自定义消息来发(TIMCustomElem), 所以无需判断, 只需获取到MsgContent即可
            JSONObject msgContent = msgBody.getJSONObject("MsgContent");

            //拿到data消息内容和desc会员ID与角色ID
            String data = msgContent.getStr("Data");
            String desc = msgContent.getStr("Desc");

            //清空MsgContent, 重新封装data消息并保存IM聊天记录。 目的是将Data改为小写data有利于前端解析
            msgContent.clear();
            msgContent.set("data", data);
            msgContent.set("desc", desc);
            msgBody.set("MsgContent", msgContent);
            imChatRecordDO.setMsgBody(msgBody);
            imChatRecordRepository.saveAndFlush(imChatRecordDO);

            //desc不为空的话, 拿到发送人会员ID与接收人会员ID
            if(StringUtils.isNotBlank(desc)) {
                JSONObject object = JSONUtil.parseObj(desc);
                String fromMemberId = object.getStr("fromMemberId");
                String toMemberId = object.getStr("toMemberId");

                imChatRecordDO.setFromMemberId(fromMemberId);
                imChatRecordDO.setToMemberId(toMemberId);
                imChatRecordRepository.saveAndFlush(imChatRecordDO);

                //
                ImSessionDO imSessionDO = imSessionRepository.findByMemberIdAndUserIdAndSourceAndSessionMemberIdAndSessionUserId(
                        Long.valueOf(toMemberId),
                        Long.valueOf(dto.getTo_Account()),
                        1,
                        Long.valueOf(fromMemberId),
                        Long.valueOf(dto.getFrom_Account())
                );
                if(imSessionDO == null) {
                    imSessionDO = new ImSessionDO();
                    imSessionDO.setMemberId(Long.valueOf(toMemberId));
                    imSessionDO.setUserId(Long.valueOf(dto.getTo_Account()));
                    imSessionDO.setSessionMemberId(Long.valueOf(fromMemberId));
                    imSessionDO.setSessionUserId( Long.valueOf(dto.getFrom_Account()));
                    imSessionDO.setSource(1);
                }
                imSessionDO.setUpdateTime(System.currentTimeMillis());
                imSessionRepository.saveAndFlush(imSessionDO);

                log.info("IM回调:接收人userId:::" + dto.getTo_Account());
                //使用webSocket向客户端推销消息, 推送人为接收消息用户
                WsMessage wsMessage = WsMessage.builder()
                        .action(WsAction.MSG_IM_MESSAGE)
                        .receiver(WsSessionKey.buildByUserId(Long.valueOf(dto.getTo_Account())))
                        .data(msgBody)
                        .build();
                wsMessagePushService.pushMessage(wsMessage);
            }

            return JSONUtil.toJsonStr(result);
        }else {
            result.put("ActionStatus", "FAIL");
            result.put("ErrorInfo", "回调命令不匹配");
            result.put("ErrorCode", 1);
            return JSONUtil.toJsonStr(result);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    @ApiOperation(value = "获取聊天记录")
    public Wrapper<PageData<ImChatRecordVO>> getChatRecord(ImGetChatRecordDTO dto) {

        log.info("接收参数--发送人用户:" + dto.getFrom());
        log.info("接收参数--接收人用户:" + dto.getTo());

        //拼接条件：where from_account = PC-1 and to_account = PC-2 or to_account = PC-1 and from_account = PC-2
        Specification<ImChatRecordDO> imChatRecordDOQuery = (root, query, criteriaBuilder) -> {
            List<Predicate> list1 = new ArrayList<>();
            List<Predicate> list2 = new ArrayList<>();

            list1.add(criteriaBuilder.equal(root.get("fromAccount"), dto.getFrom()));
            list1.add(criteriaBuilder.equal(root.get("toAccount"), dto.getTo()));

            list2.add(criteriaBuilder.equal(root.get("fromAccount"), dto.getTo()));
            list2.add(criteriaBuilder.equal(root.get("toAccount"), dto.getFrom()));

            return criteriaBuilder.or(criteriaBuilder.and(list1.toArray(new Predicate[list1.size()])), criteriaBuilder.and(list2.toArray(new Predicate[list2.size()])));
        };

        //根据条件与消息时间倒序获取聊天记录
        Pageable pageable = PageRequest.of(dto.getCurrent() - 1, dto.getPageSize(), Sort.by("msgTime").descending());
        Page<ImChatRecordDO> page = imChatRecordRepository.findAll(imChatRecordDOQuery, pageable);
        List<ImChatRecordDO> imChatRecordDOList = page.getContent();
        if(CollectionUtil.isEmpty(imChatRecordDOList)) {
            return Wrapper.success(new PageData<>(page.getTotalElements(), new ArrayList<>()));
        }

        //拼接并反转（让聊天记录从下往上）
        List<ImChatRecordVO> imChatRecordVOS = imChatRecordDOList.stream().map(a -> {
            ImChatRecordVO imChatRecordVO = new ImChatRecordVO();
            imChatRecordVO.setID(a.getId());
            imChatRecordVO.setType(a.getMsgBody().get("MsgType").toString());
            imChatRecordVO.setPayload((Map<String, Object>) a.getMsgBody().get("MsgContent"));
            imChatRecordVO.setTo(a.getToAccount());
            imChatRecordVO.setFrom(a.getFromAccount());
            imChatRecordVO.setFlow(dto.getFrom().equals(a.getFromAccount()) ? "out" : "in");
            imChatRecordVO.setTime(a.getMsgTime());
            return imChatRecordVO;
        }).collect(Collectors.toList());
        Collections.reverse(imChatRecordVOS);

        return Wrapper.success(new PageData<>(page.getTotalElements(), imChatRecordVOS));
    }
}
